package models

import (
	"errors"
	"fmt"
	"go/ast"
	"go/importer"
)

// InterfaceInfo is the interface info
type InterfaceInfo struct {
	Name            string
	Methods         []*MethodInfo
	Annotations     *Annotations
	ParentInterface *InterfaceInfo
	Package         *PackageInfo
}

func NewInterfaceInfo(name string, pkg *Package) *InterfaceInfo {
	return &InterfaceInfo{
		Name:        name,
		Annotations: NewAnnotations(),
		Package:     &pkg.PackageInfo,
	}
}

func (i *InterfaceInfo) FullName() string {
	return i.Package.Name + "." + i.Name
}

func (i *InterfaceInfo) Visit(node ast.Node) ast.Visitor {
	switch t := node.(type) {
	case *ast.CommentGroup:
		return i.Annotations
	case *ast.FieldList:
		for _, f := range t.List {
			if field, ok := f.Type.(*ast.SelectorExpr); ok {
				if pkg, ok := field.X.(*ast.Ident); ok {
					for _, imp := range i.Package.Imports {
						if imp.Name == pkg.Name {
							p, err := importer.Default().Import(imp.Path)
							if err != nil {
								panic(err)
							}
							fmt.Printf("importer: %v", p)
						}
					}
				}
			}
			(*InterfaceMethods)(&i.Methods).Visit(f)
		}
		return nil
	}
	return i
}

func (i *InterfaceInfo) GetAnnotation(name string) *Annotation {
	if i.Annotations == nil {
		return nil
	}
	a, _ := i.Annotations.Get(name)
	return a
}

func (i *InterfaceInfo) ModelName() string {
	if anno, exist := i.Annotations.Get(AnnotationName); exist {
		return anno.Value
	}
	return i.Name
}

func (i *InterfaceInfo) GetMethod(name string) (*MethodInfo, bool) {
	for _, m := range i.Methods {
		if m.Name == name {
			return m, true
		}
	}
	if i.ParentInterface != nil {
		return i.ParentInterface.GetMethod(name)
	}
	return nil, false
}

var ErrMethodConflict = errors.New("method conflict")

func (i *InterfaceInfo) ExistMethod(n *MethodInfo) bool {
	if m, exist := i.GetMethod(n.Name); exist {
		if !m.Equal(n) {
			panic(ErrMethodConflict)
		}
		return true
	}
	return false
}

func (i *InterfaceInfo) AddMethod(m *MethodInfo) {
	if !i.ExistMethod(m) && !(i.ParentInterface != nil && i.ParentInterface.ExistMethod(m)) {
		i.Methods = append(i.Methods, m)
	}
}

type InterfaceMethods []*MethodInfo

func (ms *InterfaceMethods) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.Field:
		m := &MethodInfo{}
		if n.Names != nil && len(n.Names) > 0 {
			m.Name = n.Names[0].Name
		}
		if n.Type != nil {
			ast.Walk(m, n.Type)
		}
		*ms = append(*ms, m)
		return nil
	}
	return ms
}

var BaseCrudInterface = NewInterfaceInfo("BaseCrudInterface", &Package{
	PackageInfo: PackageInfo{
		Name: "db_gorm",
		Path: "gitee.com/lipore/plume/db_gorm",
	},
})
